#ifndef _SEARCH_H_
#define _SEARCH_H_

#include <algorithm>
#include "complex_ops.h"
#include <iostream>
#include <iterator>
#include <numeric>
#include <complex>

template
<
    typename SignalSystem
>
class RandomSearch
{
public:

    RandomSearch()
    {
        r = 0.1;
        attemptCount = 0;
        scaleCount = 0;
        nextCount = 0;
    }
        
    RandomSearch(const SignalSystem& sys)
    {
        _current = sys;
        delta = sys;
        r = 0.1;
        attemptCount = 0;
        scaleCount = 0;
        nextCount = 0;
    }
    
    SignalSystem* next()
    {
        using namespace std;
           
        if (scaleCount > maxScaleCount)
            return 0;
        
        if (nextCount > 0)
        {
            scaleCount = 0;
        }
        
        if (nextCount > 2)
        {
            cout << r << endl;
            r *= 2;
        }
        
        old = _current;

        for (int i = 0; i < _current.size(); ++i)
        {
            if(nextCount == 0)
            {
                fill(delta[i].begin(), delta[i].end(), 0.0);  
            
                transform(
                    delta[i].begin(), delta[i].end(),
                    delta[i].begin(),
                    bind2nd(plus_random(), 1/r)
                );
            }
            
            transform(
                delta[i].begin(), delta[i].end(),
                _current[i].begin(), _current[i].begin(), 
                plus<complex<double>>()
            );
        }
        
        double power = 0;
        vector<double> rslt(_current[0].size());
        
        for (int i = 0; i < _current.size(); ++i)
        {
            double en = energy(_current[i]);
                
            transform(
                _current[i].begin(), _current[i].end(),
                _current[i].begin(),
                bind2nd(divides<complex<double>>(), sqrt(en))
                );
        }
        
        ++ nextCount;
        return &_current;
    }
    
    SignalSystem* current()
    {
        using namespace std;
        
        //copy(_current[0].begin(), _current[0].end(), ostream_iterator<complex<double>>(cout, " "));
        return &_current;
    }
    
    SignalSystem* prev()
    {
        nextCount = 0;
        //cout << attemptCount << endl; 
        ++ attemptCount;
        //cout << _current.size() << endl;
        if (attemptCount > 3 * _current.size() * _current[0].size())
        {
            cout << attemptCount << " " << r << endl;
            
            ++ scaleCount;
            attemptCount = 0;
            
            if(scaleCount < maxScaleCount / 4)
                r /= 2;
                
            if(scaleCount >= maxScaleCount / 4)
                r *= 2;
        }
        
        _current = old;
        
        return &_current;
    }
   
private:
    
    SignalSystem delta;
    
    SignalSystem _current;

    SignalSystem old;
    
    unsigned attemptCount;
    unsigned scaleCount;
    double r;
    
    const static unsigned maxScaleCount = 8;
    unsigned nextCount;
};

template
<
    typename SignalSystem
>
class RandomSearchX
{
public:

    RandomSearchX()
    {
        r = 0.1;
        attemptCount = 0;
        scaleCount = 0;
        nextCount = 0;
    }
        
    RandomSearchX(const SignalSystem& sys)
    {
        _current = sys;
        delta = sys;
        r = 0.1;
        attemptCount = 0;
        scaleCount = 0;
        nextCount = 0;
    }
    
    SignalSystem* next()
    {
        using namespace std;
           
        if (scaleCount > maxScaleCount)
            return 0;
        
        if (nextCount > 0)
        {
            scaleCount = 0;
        }
        
        if (nextCount > 2)
        {
            cout << r << endl;
            r *= 2;
        }
        
        old = _current;

        for (int i = 0; i < _current.size(); ++i)
        {
            if(nextCount == 0)
            {
                fill(delta[i].begin(), delta[i].end(), 0.0);  
            
                transform(
                    delta[i].begin(), delta[i].end(),
                    delta[i].begin(),
                    bind2nd(plus_random(), 1/r)
                );
            }
            
            transform(
                delta[i].begin(), delta[i].end(),
                _current[i].begin(), _current[i].begin(), 
                plus<complex<double>>()
            );
        }
        
        double power = 0;
        vector<double> rslt(_current[0].size());
        
        for (int i = 0; i < _current.size(); ++i)
        {
            double en = energy(_current[i]);
            
            transform(
                _current[i].begin(), _current[i].end(),
                _current[i].begin(),
                RandomSearchX::rotate
                );
        }
        
        for (int i = 0; i < _current.size(); ++i)
        {
            double en = energy(_current[i]);
                
            transform(
                _current[i].begin(), _current[i].end(),
                _current[i].begin(),
                bind2nd(divides<complex<double>>(), sqrt(en))
                );
        }
        
        ++ nextCount;
        return &_current;
    }
    
    SignalSystem* current()
    {
        using namespace std;
        
        //copy(_current[0].begin(), _current[0].end(), ostream_iterator<complex<double>>(cout, " "));
        return &_current;
    }
    
    SignalSystem* prev()
    {
        nextCount = 0;
        //cout << attemptCount << endl; 
        ++ attemptCount;
        //cout << _current.size() << endl;
        if (attemptCount > 3 * _current.size() * _current[0].size())
        {
            cout << attemptCount << " " << r << endl;
            
            ++ scaleCount;
            attemptCount = 0;
            
            if(scaleCount < maxScaleCount / 4)
                r /= 2;
                
            if(scaleCount >= maxScaleCount / 4)
                r *= 2;
        }
        
        _current = old;
        
        return &_current;
    }
   
    static complex<double> rotate(complex<double> x)
    {
        return polar(1.0, arg(x));
    }
    
private:
    
    SignalSystem delta;
    
    SignalSystem _current;

    SignalSystem old;
    
    unsigned attemptCount;
    unsigned scaleCount;
    double r;
    
    const static unsigned maxScaleCount = 8;
    unsigned nextCount;
};

#endif // _SEARCH_H_
